#pragma once

#include "../Factory/StackFactory.h"

////////////////////////////////////////////////////////////////////////////////////////////////////
/// <Summary>	DLList  </Summary>
/// 
/// <purpose>	Double linked list	</purpose>
////////////////////////////////////////////////////////////////////////////////////////////////////

template <typename T>
class DLList
{
	struct Node : public IFactoryAble
	{
		T data;
		Node* next;
		Node* prev;
	};

	static StackFactory<Node> nodeFactory;

public:

	class Iterator
	{
		friend class DLList<T>;

		DLList<T>* list;

		typename DLList<T>::Node* prev;
		typename DLList<T>::Node* curr;

	public:

		Iterator(DLList<T>& listToIterate, bool tail = false);

		void BeginHead();

		void BeginTail();

		bool End();

		Iterator& operator++();

		Iterator& operator--();

		T& operator*();
	};

private:

	unsigned int size;

	Node* head;
	Node* tail;

public:

	DLList();

	~DLList();

	DLList& operator=( const DLList& ref );

	DLList( const DLList& ref );

private:

	void RecursiveCopy( Node* node );

public:

	void Clear();

	void Push_Front( T item );

	void Push_Back( T item );

	T Pop_Front();

	T Pop_Back();

	void Insert(typename DLList<T>::Iterator& index, T item);

	void Erase(typename DLList<T>::Iterator& index);

	unsigned int GetSize() { return size; }
};

template<typename T>
StackFactory<typename DLList<T>::Node> DLList<T>::nodeFactory;






template<typename T>
DLList<T>::DLList() : head(NULL), tail(NULL), size(0)
{

}

template<typename T>
DLList<T>::~DLList()
{
	Clear();
}

template<typename T>
DLList<T>::DLList( const DLList<T>& ref ) : head(NULL), tail(NULL)
{
	RecursiveCopy(ref.head);
}

template<typename T>
DLList<T>& DLList<T>::operator=( const DLList<T>& ref )
{
	if(this != &ref)
	{
		Clear();
		RecursiveCopy(ref.head);
	}

	return *this;
}

template<typename T>
void DLList<T>::RecursiveCopy( Node* node )
{
	if(node)
	{
		RecursiveCopy(node->next);
		Push_Front(node->data);
	}
}

template<typename T>
void DLList<T>::Clear()
{
	Node* temp;

	while(head)
	{
		temp = head;
		head = head->next;
		DLList::nodeFactory.DeleteObject(temp);
	}
	
	size = 0;
	head = NULL;
	tail = NULL;
}

template<typename T>
void DLList<T>::Push_Front( T item )
{
	Node* temp = DLList::nodeFactory.NewObject();
	temp->data = item;

	++size;

	if(head == tail && tail)
	{
		temp->next = head;
		temp->prev = NULL;

		head = temp;
		tail->prev = head;
	}
	else if(head)
	{
		temp->next = head;
		temp->prev = NULL;

		head = temp;
		head->next->prev = head;
	}
	else
	{
		temp->next = NULL;
		temp->prev = NULL;

		head = temp;
		tail = temp;
	}
}

template<typename T>
void DLList<T>::Push_Back( T item )
{
	Node* temp = DLList::nodeFactory.NewObject();
	temp->data = item;

	++size;

	if(head == tail && head)
	{
		temp->next = NULL;
		temp->prev = tail;

		tail = temp;
		head->next = tail;
	}
	else if(tail)
	{
		temp->next = NULL;
		temp->prev = tail;

		tail = temp;
		tail->prev->next = tail;
	}
	else
	{
		temp->next = NULL;
		temp->prev = NULL;

		head = temp;
		tail = temp;
	}
}

template<typename T>
T DLList<T>::Pop_Front()
{
	if(head == tail)
	{
		T& ref = head->data;
		DLList::nodeFactory.DeleteObject(head);
		head = NULL;
		--size;
		return ref;
	}
	else
	{
		Node* temp = head;
		temp->next->prev = NULL;
		head = head->next;
		T& ref = temp->data;
		DLList::nodeFactory.DeleteObject(temp);
		--size;
		return ref;
	}
}

template<typename T>
T DLList<T>::Pop_Back()
{
	if(head == tail)
	{
		T& ref = tail->data;
		DLList::nodeFactory.DeleteObject(tail);
		tail = NULL;
		--size;
		return ref;
	}
	else
	{
		Node* temp = tail;
		temp->prev->next = NULL;
		tail = tail->prev;
		T& ref = temp->data;
		--size;
		DLList::nodeFactory.DeleteObject(temp);
		return ref;
	}
}

template<typename T>
void DLList<T>::Insert(typename DLList<T>::Iterator& index, T item)
{
	Node* temp = DLList::nodeFactory.NewObject();
	temp->data = item;
	++size;

	if(index.prev)
	{
		index.prev->next = temp;
		temp->next = index.curr;
		temp->prev = index.prev;
		index.curr->prev = temp;
		index.curr = temp;
	}
	else if (index.curr)
	{
		temp->next = head;
		temp->prev = NULL;
		head = temp;
		index.curr = head;
	}
}

template<typename T>
void DLList<T>::Erase(typename DLList<T>::Iterator& index)
{
	--size;

	if (index.curr == tail && !index.prev)
	{
		DLList::nodeFactory.DeleteObject(index.curr);
		head = NULL;
		tail = NULL;
		index.curr = head;
		index.prev = NULL;
	}
	else if (index.curr == tail)
	{
		Node* temp = tail;
		tail = index.prev;
		tail->next = NULL;
		DLList::nodeFactory.DeleteObject(temp);
	}
	else if(index.prev)
	{
		index.prev->next = index.curr->next;
		DLList::nodeFactory.DeleteObject(index.curr);
		index.curr = index.prev->next;
	}
	else if (index.curr)
	{
		Node* temp = index.curr;
		index.curr = index.curr->next;
		head = index.curr;
		head->prev = NULL;
		DLList::nodeFactory.DeleteObject(temp);
	}
}















template<typename T>
DLList<T>::Iterator::Iterator(DLList<T>& listToIterate, bool tail)
	: list(&listToIterate)
{
	if(tail)
		this->BeginTail();
	else
		this->BeginHead();
}

template<typename T>
void DLList<T>::Iterator::BeginHead()
{
	curr = list->head;
	prev = 0;
}

template<typename T>
void DLList<T>::Iterator::BeginTail()
{
	if(list->tail)
	{
		curr = list->tail;
		prev = list->tail->prev;
	}
	else
	{
		curr = list->tail;
		prev = 0;
	}
}

template<typename T>
bool DLList<T>::Iterator::End()
{
	if(curr)
		return false;
	else
		return true;
}

template<typename T>
typename DLList<T>::Iterator& DLList<T>::Iterator::operator++()
{
	if(curr)
	{
		prev = curr;
		curr = curr->next;
	}

	return *this;
}

template<typename T>
typename DLList<T>::Iterator& DLList<T>::Iterator::operator--()
{
	if(prev)
	{
		curr = curr->prev;
		prev = curr->prev;
	}
	else
	{
		curr = 0;
		prev = 0;
	}

	return *this;
}

template<typename T>
T& DLList<T>::Iterator::operator*()
{
	return curr->data;
}